package code.gateplugins.libsvm;

import gate.FeatureMap;
import gate.creole.ExecutionException;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;

import org.apache.log4j.Logger;

import libsvm.svm_model;
import libsvm.svm_node;
import libsvm.svm_problem;

public class SvmData {

	private static final Logger LOG = Logger.getLogger(SvmData.class);
	private List<Instance> instances;

	/** Holds a mapping between feature id and feature name. Libsvm uses ids. */
	private Map<String, Integer> alphabet;

	/**
	 * Holds a mapping for target classes (labels). Libsvm uses doubles which
	 * are actually ints.
	 */
	private Map<String, Integer> labelAlphabet;
	
	private svm_model model;

	public SvmData() {
		instances = new ArrayList<Instance>();
		alphabet = new HashMap<String, Integer>();
		labelAlphabet = new HashMap<String, Integer>();
	}

	public svm_node[] toNodes(FeatureMap fm) throws ExecutionException {
		// convert fm to instance
		Instance inst = getInstance(null, fm, false);
		return toNodes(inst);
	}

	public void addInstance(Object label, FeatureMap strFeatures, boolean growAlphabets) throws ExecutionException {
		Instance inst = getInstance(label, strFeatures, growAlphabets);
		instances.add(inst);
	}

	public svm_problem toSvmProblem() {
		svm_problem result = new svm_problem();
		int size = instances.size();
		result.l = size;
		result.y = new double[size];
		result.x = new svm_node[size][];
		for (int i = 0; i < size; ++i) {
			Instance inst = instances.get(i);
			result.y[i] = inst.getY();
			result.x[i] = toNodes(inst);
		}
		return result;
	}
	
	private svm_node[] toNodes(Instance inst) {
		TreeMap<Integer, Double> attrs = inst.getAttributes();
		
		// 	construct array
		svm_node[] result = new svm_node[attrs.size()];
		int i = 0;
		for (Entry<Integer, Double> f : attrs.entrySet()) {
			svm_node node = new svm_node();
			node.index = f.getKey();
			node.value = f.getValue();
			result[i] = node;
			++i;
		}
		return result;
	}

	private Instance getInstance(Object label, FeatureMap fMap, boolean growAlphabets)
			throws ExecutionException {
		double y = 0l;
		if (label != null) {
			if(label instanceof String) {
			
				String strLabel = (String)label;
				Integer alphaY = labelAlphabet.get(strLabel);
				if (alphaY == null && growAlphabets) {
					alphaY = labelAlphabet.size() + 1; // let ids start from 1
					labelAlphabet.put(strLabel, alphaY);
				} else if (alphaY == null) {
					throw new ExecutionException("Label not in the label alphabet! Value is: " + y);
				}
				y = alphaY;
			} else {
				y = (Double) label;
			}
		}
		
		TreeMap<Integer, Double> features = new TreeMap<Integer, Double>();
		for (Entry<Object, Object> feat : fMap.entrySet()) {
			String fName = (String) feat.getKey();
			double fValue = toDouble(feat.getValue());
			Integer id = alphabet.get(fName);

			if (growAlphabets) {
				if (id == null) {
					id = alphabet.size() + 1; // ids start from 1 in libsvm!
					alphabet.put(fName, id);
				}
				features.put(id, fValue);
			} else {
				// ignoring unknown features.
				if (id != null) {
					features.put(id, fValue);
				}
			}
		}
		return new Instance(y, features);
	}

	private double toDouble(Object v) {
		if (v == null) {
			LOG.warn("Feature value is null. Parsing as 0.");
			return 0;
		}
		if (v instanceof Double) {
			return (Double) v;
		} else if (v instanceof Integer) {
			return (Integer) v;
		} else if (v instanceof String) {
			return Double.parseDouble((String) v);
		}
		LOG.warn("Feature value is not of type Double, Integer or String. Parsing as 0.");
		return 0;
	}

	public Map<String, Integer> getLabelAlphabet() {
		return labelAlphabet;
	}

	public Map<String, Integer> getAlphabet() {
		return alphabet;
	}

	public void setAlphabet(Map<String, Integer> alphabet) {
		this.alphabet = alphabet;
	}

	public void setLabelAlphabet(Map<String, Integer> labelAlphabet) {
		this.labelAlphabet = labelAlphabet;
	}

	public List<Instance> getInstances() {
		return instances;
	}

	public void setInstances(List<Instance> instances) {
		this.instances = instances;
	}

	public svm_model getModel() {
		return model;
	}

	public void setModel(svm_model model) {
		this.model = model;
	}
}